﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Input;

namespace NopControls
{
    public class PolygonTabControl : ItemsControl
    {
        public event EventHandler SelectedTabChanged;

        public static readonly DependencyProperty SelectedTabProperty =
            DependencyProperty.Register("SelectedTab", typeof(int), typeof(PolygonTabControl), new PropertyMetadata(0, OnSelectedTabChanged));

        public static readonly DependencyProperty AnimationSpeedProperty =
            DependencyProperty.Register("AnimationSpeed", typeof(double), typeof(PolygonTabControl), new PropertyMetadata(300.0));

        public static readonly DependencyProperty AmplitudeProperty =
            DependencyProperty.Register("Amplitude", typeof(double), typeof(PolygonTabControl), new PropertyMetadata(0.6));

        Size requiredSize;
        double angleOffset;
        int currentWheelIndex;
        bool canMove;
        double accumulation;
        double translation;
        int indexOffset;

        double startPosition;
        bool mouseDown;
        bool blockComputeWheel;

        /// <summary>
        /// Define the amplitude of BackEase effect.
        /// </summary>
        public double Amplitude
        {
            get { return (double)GetValue(AmplitudeProperty); }
            set
            {
                SetValue(AmplitudeProperty, value);
            }
        }

        /// <summary>
        /// Define the speed of animations.
        /// </summary>
        public double AnimationSpeed
        {
            get { return (double)GetValue(AnimationSpeedProperty); }
            set
            {
                SetValue(AnimationSpeedProperty, value);
            }
        }

        /// <summary>
        /// Get or set the current tab index.
        /// </summary>
        public int SelectedTab
        {
            get { return (int)GetValue(SelectedTabProperty); }
            set
            {
                ComputeWheelIndex(value, SelectedTab);
                if (value < 0)
                    value = ItemsCount - 1;

                if (value >= ItemsCount)
                    value = 0;
                blockComputeWheel = true;
                SetValue(SelectedTabProperty, value);
                blockComputeWheel = false;
            }
        }

        /// <summary>
        /// Get items count.
        /// </summary>
        public int ItemsCount
        {
            get { return Items.Count; }
        }

        protected static void OnSelectedTabChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            PolygonTabControl polygonTabControl = sender as PolygonTabControl;

            polygonTabControl.ComputeWheelIndex((int)e.NewValue, (int)e.OldValue);

            if (polygonTabControl.SelectedTabChanged != null)
                polygonTabControl.SelectedTabChanged(polygonTabControl, EventArgs.Empty);
        }

        void ComputeWheelIndex(int newValue, int oldValue)
        {
            if (blockComputeWheel)
                return;

            indexOffset = newValue - oldValue;
            currentWheelIndex += indexOffset;
            ChangeSelectedTab();
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new Grid();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is UIElement;
        }

        /// <summary>
        /// Animation launched when selected tab changed.
        /// </summary>
        private void ChangeSelectedTab()
        {
            if (ItemsCount < 2)
                return;

            if ((Items[0] as UIElement).Projection == null)
                return;

            double newAngle = currentWheelIndex * angleOffset;

            for (int childIndex = 0; childIndex < ItemsCount; childIndex++)
            {
                UIElement child = (UIElement)Items[childIndex];
                Storyboard sb = new Storyboard();

                DoubleAnimation animation = new DoubleAnimation
                {
                    To = newAngle - childIndex * angleOffset,
                    Duration =
                        TimeSpan.FromMilliseconds(AnimationSpeed)
                };
                BackEase ease = new BackEase
                {
                    EasingMode = EasingMode.EaseOut,
                    Amplitude = Amplitude
                };
                animation.EasingFunction = ease;

                Storyboard.SetTarget(animation, child.Projection);
                Storyboard.SetTargetProperty(animation, new PropertyPath("RotationY"));

                sb.Children.Add(animation);
                sb.Begin();
            }
        }

        public PolygonTabControl()
        {
            MouseLeftButtonDown += PolygonTabControl_MouseLeftButtonDown;
            MouseLeftButtonUp += PolygonTabControl_MouseLeftButtonUp;
            MouseMove += PolygonTabControl_MouseMove;
            MouseLeave += PolygonTabControl_MouseLeave;
        }

        // Mouse handling
        void PolygonTabControl_MouseLeave(object sender, MouseEventArgs e)
        {
            FinalizeMovement();
        }

        void PolygonTabControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            FinalizeMovement();
        }

        void FinalizeMovement()
        {
            if (!mouseDown)
                return;

            mouseDown = false;
            if (!canMove)
                return;

            if (translation > 0.5 * angleOffset)
                SelectedTab--;
            else if (translation < -0.5 * angleOffset)
                SelectedTab++;
            else
            {
                ChangeSelectedTab();
            }
        }

        void PolygonTabControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (ItemsCount < 2)
                return;
            canMove = false;
            accumulation = 0;
            translation = 0;
            mouseDown = true;

            startPosition = e.GetPosition(this).X;
        }

        void PolygonTabControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!mouseDown)
                return;

            double delta = e.GetPosition(this).X - startPosition;
            accumulation += Math.Abs(delta);
            translation += delta;

            if (accumulation > 25)
            {
                canMove = true;
            }

            if (!canMove)
                return;

            for (int index = 0; index < ItemsCount; index++)
            {
                UIElement child = (UIElement)Items[index];
                (child.Projection as PlaneProjection).RotationY -= delta * (angleOffset / 360.0);
            }

            startPosition = e.GetPosition(this).X;
        }

        // Measure & Arrange
        protected override Size MeasureOverride(Size availableSize)
        {
            requiredSize = new Size(0, 0);
            for (int index = 0; index < ItemsCount; index++)
            {
                UIElement child = (UIElement)Items[index];
                child.Measure(availableSize);

                if (child.DesiredSize.Width > requiredSize.Width)
                    requiredSize.Width = child.DesiredSize.Width;
                if (child.DesiredSize.Height > requiredSize.Height)
                    requiredSize.Height = child.DesiredSize.Height;
            }

            return new Size(double.IsInfinity(availableSize.Width) ? requiredSize.Width : availableSize.Width, double.IsInfinity(availableSize.Height) ? requiredSize.Height : availableSize.Height);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            angleOffset = 360.0 / ItemsCount;
            double radiansAngle = Math.PI / ItemsCount;
            double newAngle = currentWheelIndex * angleOffset;

            double radius = ((finalSize.Width / 2) / Math.Tan(radiansAngle));

            if (ItemsCount == 2)
                radius += 1.0;

            if (requiredSize == new Size(0, 0))
                requiredSize = finalSize;

            int childIndex = 0;
            for (int index = 0; index < ItemsCount; index++)
            {
                UIElement child = (UIElement)Items[index];
                child.Arrange(new Rect(0, 0, Math.Min(requiredSize.Width, finalSize.Width), Math.Min(requiredSize.Height, finalSize.Height)));
                PlaneProjection projection = new PlaneProjection { CenterOfRotationZ = -radius, RotationY = newAngle - childIndex * angleOffset };
                child.Projection = projection;

                childIndex++;
            }

            return finalSize;
        }
    }
}
